from pylab import *
from numpy import *
from copy import *
from hash import *
from time import sleep
import math

class Oscillator:
  osc_rapams    = [];
  osc_vals_P    = [];
  osc_vals_M    = [];
  osc_times     = [];
  osc_dt        = 1;
  
  osc_steps     = 0;
  osc_tausteps  = 0;

  osc_peaks   = [];
  osc_lows    = [];

  osc_times_up = [];
  osc_times_down = [];

  osc_osc             = 0;
  osc_amp             = 0;
  osc_frq             = 0;
  osc_amp_std         = 0;
  osc_frq_std         = 0;
  osc_eps             = 0.9;
  osc_tim_up	        = 0;
  osc_tim_down 		    = 0;
  osc_tim_up_std	    = 0;
  osc_tim_down_std 	  = 0;

  
  #ZAOKROZEVALNA KONSTANTA
  osc_round   = 3;
  osc_ampmax  = 0;

  #HASH TABELA
  osc_hash = None
  


  def __init__(self,r,t,dt,rnd, am):
    self.osc_rapams     = r;
    self.osc_dt         = dt;
    self.osc_steps      = int(math.ceil(t/dt));
    self.osc_tausteps   = int(math.ceil(self.osc_rapams[0]/dt));
    self.osc_times      = [i*dt for i in range(0,self.osc_steps)];
    self.osc_round      = rnd;
    self.osc_hash       = HashTable(self.osc_ampmax,self.osc_round)
    self.osc_ampmax     = am;
    self.run();

  def get_times(self):
    return self.osc_times;
  
  def get_vals(self):
    return self.osc_vals_P;

  def get_mrna(self):
    return self.osc_vals_M;

  def get_amp(self):
    return self.osc_amp;

  def get_frq(self):
    return self.osc_frq;

  def get_per(self):
    return 1.0/self.osc_frq;

  def is_osc(self):
    return self.osc_osc;

  def get_delayed_m(self,s,ds):
    if s-ds < 0:
      return 0
    else:
      return self.osc_vals_M[int(s-ds)];

  def get_delayed_p(self,s,ds):
    if s-ds < 0:
      return 0
    else:
      return self.osc_vals_P[int(s-ds)];

  def get_tim_up(self):
    #return [self.osc_tim_up,self.osc_tim_up_std];
    return self.osc_tim_up;

  def get_tim_down(self):
    #return [self.osc_tim_down,self.osc_tim_down_std];
    return self.osc_tim_down;

  def get_rapams(self):
    return self.osc_rapams;
  
  def get_peaks(self):
    return self.osc_peaks;
  
  def run(self):
  
    rp =  self.osc_rapams[1]
    rm =  self.osc_rapams[2]
    qp =  self.osc_rapams[3]
    qm =  self.osc_rapams[4]
    m =   self.osc_rapams[5]
    n =   self.osc_rapams[6]
    kn =   self.osc_rapams[7]
    self.osc_vals_M     = [0 for i in range(0,self.osc_steps)];
    self.osc_vals_P     = [0 for i in range(0,self.osc_steps)];
    self.osc_peaks   = [];
    self.osc_lows    = [];
    self.osc_times_up = [];
    self.osc_times_down = [];

    osc_teststack = [];
    osc_timeout = self.osc_tausteps;

    for t in range(0,self.osc_steps):
      
      #dm = rm / (1+(P(i-1)/k)^n) - qm*M(i-1);
      #dp = rp*M(i-di)^m -qp*P(i-1);
      try:  
        dm = rm / (1+(self.get_delayed_p(t,1)**n/kn)) - qm * self.get_delayed_m(t,1);
        dp = rp*self.get_delayed_m(t,self.osc_tausteps)**m - qp*self.get_delayed_p(t,1);
      except OverflowError:
        print 'Overflow!'
        return 0
      #dm = max([0,rm / (1+(self.get_delayed_p(t,1)/k)**n) - qm * self.get_delayed_m(t,1)]);
      #dp = max([0,rp*self.get_delayed_m(t,self.osc_tausteps)**m - qp*self.get_delayed_p(t,1)]);
      self.osc_vals_M[t] = max([0,round(self.get_delayed_m(t,1)+dm*self.osc_dt,self.osc_round)]);
      self.osc_vals_P[t] = max([0,round(self.get_delayed_p(t,1)+dp*self.osc_dt,self.osc_round)]);
      #self.osc_vals_M[t] = max([0,self.get_delayed_m(t,1)+dm*self.osc_dt]);
      #self.osc_vals_P[t] = max([0,self.get_delayed_p(t,1)+dp*self.osc_dt]);


      if self.osc_osc == 0:
        pt=[self.get_delayed_p(t,1), self.get_delayed_m(t,1), self.get_delayed_m(t,self.osc_tausteps)];
        self.osc_osc = self.osc_hash.insert(pt);
#      else:
				#Free hash table
#        self.osc_hash = None

      #zaznavamo stagniranje v intervalu tau
      #print 'timeout'
      #print osc_timeout;
      #if self.osc_vals_P[t] == self.get_delayed_p(t,1):
      if self.osc_vals_P[t] == self.get_delayed_p(t,1):
        osc_timeout = osc_timeout - 1;
        if osc_timeout < 0: 
          self.osc_osc = 0;
      else:
        osc_timeout = self.osc_tausteps;
        


      if t<2: 
        continue;
        
      # ce so vrhovi in doline ravni, vzamemo prvo tocko ravnine za vrh
			# najprej gremo gor, nato dol
      if (self.osc_vals_P[t-1] > self.osc_vals_P[t] and self.osc_vals_P[t-1] > self.osc_vals_P[t-2]) or (self.osc_vals_P[t-1] > self.osc_vals_P[t] and self.osc_vals_P[t-1] >= self.osc_vals_P[t-2]):
         self.osc_peaks = self.osc_peaks + [t-1];

      if (self.osc_vals_P[t-1] < self.osc_vals_P[t] and self.osc_vals_P[t-1] < self.osc_vals_P[t-2]) or (self.osc_vals_P[t-1] <= self.osc_vals_P[t] and self.osc_vals_P[t-1] < self.osc_vals_P[t-2]):
          self.osc_lows = self.osc_lows + [t-1];

    ### END FOR
    ### ZACETEK IZRACUNAVANJA LASTNOSTI



    #print self.osc_vals_P
    #print self.osc_peaks;
    #print self.osc_lows;
    df = [];
    da = [];
    dtu = [];
    dtd = [];
  

    for i in range(len(self.osc_peaks)):
      if i == len(self.osc_peaks)-1:
        break;

      da = da + [math.fabs(self.osc_vals_P[self.osc_peaks[i]]-self.osc_vals_P[self.osc_lows[i]])];
      #da = da + [self.osc_vals_P[self.osc_peaks[i]]];
      #df = df + [math.fabs((self.osc_peaks[i+1]-self.osc_peaks[i])*self.osc_dt)];
      df = df + [1.0/(math.fabs((self.osc_peaks[i+1]-self.osc_peaks[i])*self.osc_dt))];
      dtu = dtu + [math.fabs((self.osc_peaks[i+1]-self.osc_lows[i])*self.osc_dt)];
      dtd = dtd + [math.fabs((self.osc_lows[i]-self.osc_peaks[i])*self.osc_dt)];
	
    '''  
    print 'peaks: ', self.osc_peaks;
    print 'lows: ', self.osc_lows;
    print 'd amp: ', da
    print 'd frq: ',  df
    print 'd times up: ', dtu
    print 'd times down: ',dtd
	'''
    #amplituda je povprecna visina vrhov
    self.osc_amp = mean(da);
    self.osc_amp_std = std(da);

    #frekvenca je povprecna razlika med oddaljenostvo vrhov
    self.osc_frq = 1.0/mean(df)
    self.osc_frq = mean(df)
    #self.osc_frq_std = 1.0/std(df);

	  #povprecen cas vzpona in padca
    self.osc_times_up 	= dtu;  	
    self.osc_times_down = dtd;
    self.osc_tim_up = mean(dtu);
    self.osc_tim_down = mean(dtd);
    self.osc_tim_up_std = std(dtu);
    self.osc_tim_down_std = std(dtd);

    #print self.osc_amp_std;
    #print self.osc_frq_std;

    #kriteriji za 'lepe' oscilacije:
    #stabilna amplituda (min std. dev)
    #stabilna frekvenca (min std. dev)
    #limitni cikel
    #minimalna razlika med vzponom in padcem
    #self.test_osc();

    return 1
    

  def test_osc(self):
    if self.osc_osc == 1:
      if self.osc_amp>0 and self.osc_frq >0:
        return;
      else:
        self.osc_osc = 0;
   
  def dbgPlot(self):
    #plot(self.osc_times, self.osc_vals_M, linewidth=1.0)
    #plot(self.osc_times, self.osc_vals_P, linewidth=1.0)
    plot(self.osc_vals_M, self.osc_vals_P, linewidth=1.0)
    xlabel('time')
    ylabel('vals')
    title(str(self))
    grid(True)
    show()

  def __str__(self):
    return "Oscillating: "+str(self.osc_osc)+", Amp: "+str(self.osc_amp)+", Per: "+str(1.0/self.osc_frq) + " [h]";
